# Copyright 2024 Henrique Paone
#
# This file is part of Kitty-Visual.
#
# Kitty-Visual is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
# 
# Kitty-Visual is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along with
# Kitty-Visual. If not, see <https://www.gnu.org/licenses/>.

require_relative './tui.rb'
require_relative './kitty.rb'

class Screen
  include Tui
  include Kitty

  @@root = "#{ Dir.tmpdir }/.kitty-visual"
  
  attr_reader :contents_wrapped, :first
  attr_accessor :height, :width, :cursor, :selection, :match, :contents, :resized

  def initialize(sel_color, search_color, height, width, scrolled = 0, cline = 1, ccol = 1, contents = '')
    at_exit { kitty_restore } 

    kitty_keyboard_enable(
      :disambiguate_escape_codes,
      :associated_text,
      :all_escaped
    )

    @resized = false

    @widest = contents.max.size
    @contents = @contents_wrapped = contents
    
    @sel_color = sel_color
    @search_color = search_color
    @selection = []
    @match = []
    
    @height = height
    @width = width
    
    @first = @contents.size - @height
    @first = 0 if @first < 1
    @cursor = [line_to_buf(cline), ccol]
    if scrolled.nonzero?
      @first = @first - scrolled + 1
      @cursor = [@first, 1]
    end
    wrap_lines
    update
  end

  def update
    line, col = @cursor
    last = @first + @height
    if line > @first
      if line > last
        @first = line - @height
      end
    else
      @first = line - 1
    end
    visible_lines = @contents_wrapped[@first, @height]
    STDOUT.clear_screen
    if @resized 
      print_cursor_explicit visible_lines
      @resized = false
    else 
      print visible_lines.join "\n"
    end
    highlight @selection unless @selection.empty?  
    highlight @match
    move_cursor(line_to_screen(line), col)
  end

  def width=(width)
    @width = width
    wrap_lines
  end

  def wrap_lines
    regex = /#{Ansi_regex}|#{Kitty_builtin_regex}/
    wrapped = []
    @contents.each do |line|
      length = line.gsub(/#{ regex }/, '').size
      if length > @width
        wrapped_line = escseq = ''
        i = 0
        while i < line.size do
          if wrapped_line.gsub(regex, '').size > @width - 1
            wrapped << wrapped_line
            wrapped_line = ''
          end
          if line[i] == "\e"
            while not regex.match? escseq do
              escseq += line[i]
              i += 1
            end
            wrapped_line += escseq
            escseq = ''
          else 
            wrapped_line += line[i]
            i += 1
          end
        end
        wrapped << wrapped_line unless wrapped_line.empty?
      else
        wrapped << line
      end
    end
    dif = wrapped.size - @contents_wrapped.size
    unless dif.zero?
      if dif.negative?
        dif = dif.abs
        @first -= dif unless @first < 1
        @cursor[0] -= dif unless @cursor[0] - dif < 1
      else
        @first += dif unless @first > wrapped.size
        @cursor[0] += dif unless @cursor[0] + dif > wrapped.size
      end
    end
    @contents_wrapped = wrapped
  end
  
  def match(pattern, scrollback)
    matches = []
    regex = Regexp.new(pattern)
    scrollback.each_with_index do |line, line_num|
      match = line.match(regex)
      match_str = match.to_s
      unless match_str.empty?
        cstart = match.begin(0)
        cstop = match_str.size + cstart
        matches << [line_num + 1, cstart + 1, cstop]
      end
    end
    @match = matches
  end

  def highlight(lines)
    last = @first + height
    lines.each do |line|
      next unless line[0].between? @first, last
      l, *cols = line
      hi_line line_to_screen(l), *cols, @sel_color
    end
    redraw
  end

  def line_to_screen(line)
    dif = line - @first
    dif.zero? ? 1 : dif
  end

  def line_to_buf(line)
    line + @first
  end

  def print_cursor_explicit(lines)
    line_count = 0
    lines.each do |line|
      print line + "\n"
      move_cursor(line_count += 1, 1)
    end
    # For some reason, after resizing there's the screen moves down one line, so
    # we unscroll, then print the missing line.
    unscroll 1
    move_cursor 1, 1
    print lines[0]
  end

  private :wrap_lines
end
